﻿using System;
using GlmNet;
using SharpGL;
using SharpGL.Shaders;
using SharpGL.VertexBuffers;
using System.IO;
using System.Windows;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing;

namespace ModernOpenGLSample
{
    /// <summary>
    /// Represents the Scene for this sample.
    /// </summary>
    /// <remarks>
    /// This code is based on work from the OpenGL 4.x Swiftless tutorials, please see:
    /// http://www.swiftless.com/opengl4tuts.html
    /// </remarks>
    public class Scene {
        //  The projection, view and model matrices.
        mat4 projectionMatrix;
        mat4 viewMatrix;
        mat4 modelMatrix;

        //  Constants that specify the attribute indexes.
        const uint attributeIndexPosition = 0;
        const uint attributeIndexColour = 1;

        //  The vertex buffer array which contains the vertex and colour buffers.
        VertexBufferArray vertexBufferArray;

        //  The shader program for our vertex and fragment shader.
        private ShaderProgram shaderProgram;

        /// <summary>
        /// Initialises the scene.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="width">The width of the screen.</param>
        /// <param name="height">The height of the screen.</param>
        public struct pos<T> {
            public pos(T x, T y, T z) {
                this.x = x;
                this.y = y;
                this.z = z;
            }
            public T x, y, z;
        };

        private ShaderProgram ShadowMapProgram, SelectShaderProgram;
        static float[] vertex_positions = new float[10000000];
        static float[] vertex_normals = new float[10000000];
        static float[] vertex_texCoord = new float[10000000];
        static float[] vertex_color = new float[10000000];
        static float[] vertex_objectid = new float[10000000];
        List<pos<float>> v = new List<pos<float>>();
        List<pos<float>> vn = new List<pos<float>>();
        List<pos<float>> vt = new List<pos<float>>();
        List<pos<uint>> face = new List<pos<uint>>();
        List<pos<uint>> vtid = new List<pos<uint>>();
        static int facesNum = 0;
        private uint[] depth_fbo = new uint[1];
        SharpGL.SceneGraph.Assets.Texture depth_texture = new SharpGL.SceneGraph.Assets.Texture();
        SharpGL.SceneGraph.Assets.Texture lightSettingTex = new SharpGL.SceneGraph.Assets.Texture();
        public Bitmap lightSettingBitmap = new Bitmap(3, 100);
        static int objNum = 0;
        static float[] ambient = new float[100];
        static float[] diffuse = new float[100];
        static float[] specular = new float[100];
        static float[] visible = new float[100];
        struct Object {
            public int id;
            public int pos;
            public int length;
            public pos<float> offset;
        };
        string[] objName = new string[100];

        Dictionary<string, Object> obj=new Dictionary<string, Object>();
        public void Move(OpenGL gl, string name, pos<float> offset) {
            for (int i = 0; i < obj[name].length; i++) {
                var x = vertex_positions[obj[name].pos * 12 + i * 12];
                vertex_positions[obj[name].pos * 12 + i * 12] += offset.x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 1] += offset.y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 2] += offset.z;
                vertex_positions[obj[name].pos * 12 + i * 12 + 4] += offset.x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 5] += offset.y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 6] += offset.z;
                vertex_positions[obj[name].pos * 12 + i * 12 + 8] += offset.x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 9] += offset.y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 10] += offset.z;
            }
            IntPtr p = Marshal.AllocHGlobal(vertex_positions.Length * sizeof(float));
            Marshal.Copy(vertex_positions, 0, p, vertex_positions.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, 0, vertex_positions.Length, p);
            Marshal.FreeHGlobal(p);
        }
        public void Move(OpenGL gl, int id, pos<float> offset) {
            Move(gl, objName[id], offset);
        }
        public void Delete(OpenGL gl, int id) {
            Delete(gl, objName[id]);
        }
        public void Hide(int id) {
            visible[id] = 0;
        }

        public void Show(int id) {
            visible[id] = 1;
        }
        public void Delete(OpenGL gl, string name) {
            for (int i = obj[name].pos; i < facesNum; i++) {
                vertex_positions[i * 12] = vertex_positions[i * 12 + obj[name].length * 12];
                vertex_positions[i * 12 + 1] = vertex_positions[i * 12 + 1 + obj[name].length * 12];
                vertex_positions[i * 12 + 2] = vertex_positions[i * 12 + 2 + obj[name].length * 12];
                vertex_positions[i * 12 + 4] = vertex_positions[i * 12 + 4 + obj[name].length * 12];
                vertex_positions[i * 12 + 5] = vertex_positions[i * 12 + 5 + obj[name].length * 12];
                vertex_positions[i * 12 + 6] = vertex_positions[i * 12 + 6 + obj[name].length * 12];
                vertex_positions[i * 12 + 8] = vertex_positions[i * 12 + 8 + obj[name].length * 12];
                vertex_positions[i * 12 + 9] = vertex_positions[i * 12 + 9 + obj[name].length * 12];
                vertex_positions[i * 12 + 10] = vertex_positions[i * 12 + 10 + obj[name].length * 12];
                vertex_normals[i * 12] = vertex_normals[i * 12 + obj[name].length * 12];
                vertex_normals[i * 12 + 1] = vertex_normals[i * 12 + obj[name].length * 12 + 1];
                vertex_normals[i * 12 + 2] = vertex_normals[i * 12 + obj[name].length * 12 + 2];
                vertex_normals[i * 12 + 3] = vertex_normals[i * 12 + obj[name].length * 12 + 3];
                vertex_normals[i * 12 + 4] = vertex_normals[i * 12 + obj[name].length * 12 + 4];
                vertex_normals[i * 12 + 5] = vertex_normals[i * 12 + obj[name].length * 12 + 5];
                vertex_normals[i * 12 + 6] = vertex_normals[i * 12 + obj[name].length * 12 + 6];
                vertex_normals[i * 12 + 7] = vertex_normals[i * 12 + obj[name].length * 12 + 7];
                vertex_normals[i * 12 + 8] = vertex_normals[i * 12 + obj[name].length * 12 + 8];
                vertex_normals[i * 12 + 9] = vertex_normals[i * 12 + obj[name].length * 12 + 9];
                vertex_normals[i * 12 + 10] = vertex_normals[i * 12 + obj[name].length * 12 + 10];
                vertex_normals[i * 12 + 11] = vertex_normals[i * 12 + obj[name].length * 12 + 11];
                vertex_color[i * 12] = vertex_color[i * 12 + obj[name].length * 12];
                vertex_color[i * 12 + 1] = vertex_color[i * 12 + obj[name].length * 12 + 1];
                vertex_color[i * 12 + 2] = vertex_color[i * 12 + obj[name].length * 12 + 2];
                vertex_color[i * 12 + 4] = vertex_color[i * 12 + obj[name].length * 12 + 4];
                vertex_color[i * 12 + 5] = vertex_color[i * 12 + obj[name].length * 12 + 5];
                vertex_color[i * 12 + 6] = vertex_color[i * 12 + obj[name].length * 12 + 6];
                vertex_color[i * 12 + 8] = vertex_color[i * 12 + obj[name].length * 12 + 8];
                vertex_color[i * 12 + 9] = vertex_color[i * 12 + obj[name].length * 12 + 9];
                vertex_color[i * 12 + 10] = vertex_color[i * 12 + obj[name].length * 12 + 10];
                vertex_objectid[i * 3] = objNum - 1;
                vertex_objectid[i * 3 + 1] = objNum - 1;
                vertex_objectid[i * 3 + 2] = objNum - 1;
            }
            objName[obj[name].id] = objName[obj[name].id + 1];
            ambient[obj[name].id] = ambient[obj[name].id + 1];
            diffuse[obj[name].id] = diffuse[obj[name].id + 1];
            specular[obj[name].id] = specular[obj[name].id + 1];
            visible[obj[name].id] = visible[obj[name].id + 1];
            for (int i = obj[name].id + 1; i < objNum; i++) {
                ambient[i] = ambient[i + 1];
                diffuse[i] = diffuse[i + 1];
                specular[i] = specular[i + 1];
                visible[i] = visible[i + 1];
                Object o = new Object();
                o.id = obj[objName[i]].id - 1;
                o.length = obj[objName[i]].length;
                o.pos = obj[objName[i]].pos - obj[name].length;
                o.offset = obj[objName[i]].offset;
                obj[objName[i]] = o;
                objName[i] = objName[i+1];
            }
            objNum--;
            facesNum -= obj[name].length;
            obj[name] = new Object();
            IntPtr p = Marshal.AllocHGlobal(vertex_positions.Length * sizeof(float));
            Marshal.Copy(vertex_positions, 0, p, vertex_positions.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, 0, vertex_positions.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_positions.Length * sizeof(float));
            Marshal.Copy(vertex_normals, 0, p, vertex_normals.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, vertex_positions.Length, vertex_normals.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_color.Length * sizeof(float));
            Marshal.Copy(vertex_color, 0, p, vertex_color.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, vertex_positions.Length + vertex_normals.Length, vertex_color.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_objectid.Length * sizeof(float));
            Marshal.Copy(vertex_objectid, 0, p, vertex_objectid.Length);
            Marshal.FreeHGlobal(p);
        }
        public void Loader(string target,pos<float> offset,pos<float> color,float ambientStrenth, float specularStrenth, float diffuseStrenth,string name) {
            ambient[objNum] = ambientStrenth;
            specular[objNum] = specularStrenth;
            diffuse[objNum] = diffuseStrenth;
            visible[objNum] = 1;
            Bitmap b = new Bitmap(lightSettingBitmap);
            StreamReader read = new StreamReader(target);
            v = new List<pos<float>>();
            vn = new List<pos<float>>();
            vt = new List<pos<float>>();
            face = new List<pos<uint>>();
            vtid = new List<pos<uint>>();
            while (!read.EndOfStream) {
                string str;
                str=read.ReadLine();
                float x, y, z;
                if (str.Split(' ')[0].Equals("v")) {
                    x = float.Parse(str.Split(' ')[1]) + offset.x;
                    y = float.Parse(str.Split(' ')[2]) + offset.y;
                    z = float.Parse(str.Split(' ')[3]) + offset.z;
                    pos<float> t=new pos<float>(x,y,z);
                    v.Add(t);
                } else if (str.Split(' ')[0].Equals("vn")) {
                    Random rand = new Random();
                    x = float.Parse(str.Split(' ')[1]);
                    y = float.Parse(str.Split(' ')[2]);
                    z = float.Parse(str.Split(' ')[3]);
                    pos<float> t = new pos<float>(x, y, z);
                    vn.Add(t);
                } else if (str.Split(' ')[0].Equals("f")) {
                    x = uint.Parse(str.Split(' ')[1].Split('/')[0]);
                    y = uint.Parse(str.Split(' ')[2].Split('/')[0]);
                    z = uint.Parse(str.Split(' ')[3].Split('/')[0]);
                    pos<uint> t = new pos<uint>((uint)x, (uint)y, (uint)z);
                    face.Add(t);
                    x = uint.Parse(str.Split(' ')[1].Split('/')[2]);
                    y = uint.Parse(str.Split(' ')[2].Split('/')[2]);
                    z = uint.Parse(str.Split(' ')[3].Split('/')[2]);
                    t = new pos<uint>((uint)x, (uint)y, (uint)z);
                    vtid.Add(t);
                } else if (str.Split(' ')[0].Equals("vt")) {
                    x = float.Parse(str.Split(' ')[1].Split('/')[0]);
                    y = float.Parse(str.Split(' ')[2].Split('/')[0]);
                    z = float.Parse(str.Split(' ')[3].Split('/')[0]);
                    pos<float> t = new pos<float>((float)x, (float)y, (float)z);
                    vt.Add(t);
                }
            }

            Object o = new Object();
            o.id = objNum;
            o.length = face.Count;
            o.pos = facesNum;
            o.offset = offset;
            objName[objNum] = name;
            obj[name] = o;
            for (int i = 0; i < face.Count; i++) {
                vertex_color[obj[name].pos * 12 + i * 12] = color.x;
                vertex_color[obj[name].pos * 12 + i * 12 + 1] = color.y;
                vertex_color[obj[name].pos * 12 + i * 12 + 2] = color.z;
                vertex_color[obj[name].pos * 12 + i * 12 + 3] = 1.0f;
                vertex_color[obj[name].pos * 12 + i * 12 + 4] = color.x;
                vertex_color[obj[name].pos * 12 + i * 12 + 5] = color.y;
                vertex_color[obj[name].pos * 12 + i * 12 + 6] = color.z;
                vertex_color[obj[name].pos * 12 + i * 12 + 7] = 1.0f;
                vertex_color[obj[name].pos * 12 + i * 12 + 8] = color.x;
                vertex_color[obj[name].pos * 12 + i * 12 + 9] = color.y;
                vertex_color[obj[name].pos * 12 + i * 12 + 10] = color.z;
                vertex_color[obj[name].pos * 12 + i * 12 + 11] = 1.0f;
                vertex_positions[obj[name].pos * 12 + i * 12] = v[(int)face[i].x - 1].x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 1] = v[(int)face[i].x - 1].y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 2] = v[(int)face[i].x - 1].z;
                vertex_positions[obj[name].pos * 12 + i * 12 + 3] = 1.0f;
                vertex_positions[obj[name].pos * 12 + i * 12 + 4] = v[(int)face[i].y - 1].x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 5] = v[(int)face[i].y - 1].y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 6] = v[(int)face[i].y - 1].z;
                vertex_positions[obj[name].pos * 12 + i * 12 + 7] = 1.0f;
                vertex_positions[obj[name].pos * 12 + i * 12 + 8] = v[(int)face[i].z - 1].x;
                vertex_positions[obj[name].pos * 12 + i * 12 + 9] = v[(int)face[i].z - 1].y;
                vertex_positions[obj[name].pos * 12 + i * 12 + 10] = v[(int)face[i].z - 1].z;
                vertex_positions[obj[name].pos * 12 + i * 12 + 11] = 1.0f;
                vertex_normals[obj[name].pos * 12 + i * 12] = vn[(int)vtid[i].x - 1].x;
                vertex_normals[obj[name].pos * 12 + i * 12 + 1] = vn[(int)vtid[i].x - 1].y;
                vertex_normals[obj[name].pos * 12 + i * 12 + 2] = vn[(int)vtid[i].x - 1].z;
                vertex_normals[obj[name].pos * 12 + i * 12 + 4] = vn[(int)vtid[i].y - 1].x;
                vertex_normals[obj[name].pos * 12 + i * 12 + 5] = vn[(int)vtid[i].y - 1].y;
                vertex_normals[obj[name].pos * 12 + i * 12 + 6] = vn[(int)vtid[i].y - 1].z;
                vertex_normals[obj[name].pos * 12 + i * 12 + 8] = vn[(int)vtid[i].z - 1].x;
                vertex_normals[obj[name].pos * 12 + i * 12 + 9] = vn[(int)vtid[i].z - 1].y;
                vertex_normals[obj[name].pos * 12 + i * 12 + 10] = vn[(int)vtid[i].z - 1].z;
                vertex_objectid[obj[name].pos * 3 + i * 3] = objNum;
                vertex_objectid[obj[name].pos * 3 + i * 3 + 1] = objNum;
                vertex_objectid[obj[name].pos * 3 + i * 3 + 2] = objNum;
                /*vertex_texCoord[i * 9] = vt[(int)vtid[i].x - 1].x;
                vertex_texCoord[i * 9 + 1] = vt[(int)vtid[i].x - 1].y;
                vertex_texCoord[i * 9 + 2] = vt[(int)vtid[i].x - 1].z;
                vertex_texCoord[i * 9 + 3] = vt[(int)vtid[i].y - 1].x;
                vertex_texCoord[i * 9 + 4] = vt[(int)vtid[i].y - 1].y;
                vertex_texCoord[i * 9 + 5] = vt[(int)vtid[i].y - 1].z;
                vertex_texCoord[i * 9 + 6] = vt[(int)vtid[i].z - 1].x;
                vertex_texCoord[i * 9 + 7] = vt[(int)vtid[i].z - 1].y;
                vertex_texCoord[i * 9 + 8] = vt[(int)vtid[i].z - 1].z;*/
            }

            facesNum += face.Count;
            objNum++;
        }
        public unsafe void Initialise(OpenGL gl, float width, float height)
        {
            //  Set a blue clear colour.
            gl.ClearColor(0.4f, 0.6f, 0.9f, 0.0f);
            Loader("ground.obj", new pos<float>(0, 0, 0), new pos<float>(0.5f, 0.5f, 0.5f), 0.5f, 0.1f, 30, "ground");
            Loader("ball.obj", new pos<float>(0, 10, 0), new pos<float>(0.5f, 0.5f, 0.5f), 0.5f, 2f, 30,"b1");
            Loader("ball.obj", new pos<float>(0, 15, 5), new pos<float>(0.5f, 0.5f, 0.5f), 1f, 1f, 30,"b2");
            Loader("arrow.obj", new pos<float>(0, 20, 10), new pos<float>(0.5f, 0.5f, 0.5f), 0.5f, 3f, 30, "arrowy");
            Loader("ball.obj", new pos<float>(-Camera.lightx, -Camera.lighty, -Camera.lightz), new pos<float>(10, 10, 10), 100, 100, 100,"light");
            Loader("ball.obj", new pos<float>(0, 20, 10), new pos<float>(0.5f, 0.5f, 0.5f), 0.5f, 3f, 30, "b3");
            //  Create the shader program.
            var vertexShaderSource = ManifestResourceLoader.LoadTextFile("Shader.vert");
            var fragmentShaderSource = ManifestResourceLoader.LoadTextFile("Shader.frag");
            var vertexShadowMapSource = ManifestResourceLoader.LoadTextFile("ShadowMap.vert");
            var fragmentShadowMapSource = ManifestResourceLoader.LoadTextFile("ShadowMap.frag");
            var vertexSelectShaderSource = ManifestResourceLoader.LoadTextFile("SelectShader.vert");
            var fragmentSelectShaderSource = ManifestResourceLoader.LoadTextFile("SelectShader.frag");
            var vertexSimpleShaderSource = ManifestResourceLoader.LoadTextFile("SimpleShader.vert");
            var fragmentSimpleShaderSource = ManifestResourceLoader.LoadTextFile("SimpleShader.frag");
            shaderProgram = new ShaderProgram();
            shaderProgram.Create(gl, vertexShaderSource, fragmentShaderSource, null);
            shaderProgram.BindAttributeLocation(gl, 0, "position");
            shaderProgram.BindAttributeLocation(gl, 1, "normal");
            shaderProgram.BindAttributeLocation(gl, 2, "color");
            shaderProgram.BindAttributeLocation(gl, 3, "id");
            shaderProgram.AssertValid(gl);
            ShadowMapProgram = new ShaderProgram();
            ShadowMapProgram.Create(gl, vertexShadowMapSource, fragmentShadowMapSource, null);
            ShadowMapProgram.BindAttributeLocation(gl, 0, "position");
            ShadowMapProgram.BindAttributeLocation(gl, 1, "normal");
            ShadowMapProgram.BindAttributeLocation(gl, 2, "color");
            ShadowMapProgram.AssertValid(gl);
            SelectShaderProgram = new ShaderProgram();
            SelectShaderProgram.Create(gl, vertexSelectShaderSource, fragmentSelectShaderSource, null);
            SelectShaderProgram.BindAttributeLocation(gl, 0, "position");
            SelectShaderProgram.BindAttributeLocation(gl, 1, "normal");
            SelectShaderProgram.BindAttributeLocation(gl, 2, "color");
            shaderProgram.BindAttributeLocation(gl, 3, "id");
            SelectShaderProgram.AssertValid(gl);
            SimpleShaderProgram = new ShaderProgram();
            SimpleShaderProgram.Create(gl, vertexSimpleShaderSource, fragmentSimpleShaderSource, null);
            SimpleShaderProgram.BindAttributeLocation(gl, 0, "position");
            SimpleShaderProgram.BindAttributeLocation(gl, 1, "normal");
            SimpleShaderProgram.BindAttributeLocation(gl, 2, "color");
            SimpleShaderProgram.AssertValid(gl);

            //  Create a perspective projection matrix.
            const float rads = (90.0f / 360.0f) * (float)Math.PI * 2.0f;
            projectionMatrix = glm.perspective(rads, width / height, 0.1f, 100.0f);

            //  Create a view matrix to move us back a bit.
            viewMatrix = glm.translate(new mat4(1.0f), new vec3((float)Camera.x, (float)Camera.y, (float)Camera.z));

            //  Create a model matrix to make the model a little bigger.
            modelMatrix = glm.scale(new mat4(1.0f), new vec3(1.0f));




            //  Now create the geometry for the square. 
            uint[] vao=new uint[1];
            uint[] vbo=new uint[1];
            uint[] ebo=new uint[1];
            gl.GenBuffers(1, ebo);
            gl.BindBuffer(OpenGL.GL_ELEMENT_ARRAY_BUFFER, ebo[0]);
            // Set up the vertex attributes
            gl.GenVertexArrays(1, vao);
            gl.BindVertexArray(vao[0]);
            
            gl.GenBuffers(1, vbo);
            gl.BindBuffer(OpenGL.GL_ARRAY_BUFFER, 0);
            gl.BindBuffer(OpenGL.GL_ARRAY_BUFFER, vbo[0]);
            
            gl.BufferData(OpenGL.GL_ARRAY_BUFFER, (vertex_positions.Length + vertex_normals.Length +vertex_color.Length + vertex_objectid.Length) * sizeof(float), (IntPtr)null, OpenGL.GL_STATIC_DRAW);
            
            IntPtr p = Marshal.AllocHGlobal(vertex_positions.Length * sizeof(float));
            Marshal.Copy(vertex_positions, 0, p, vertex_positions.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, 0, vertex_positions.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_positions.Length * sizeof(float));
            Marshal.Copy(vertex_normals, 0, p, vertex_normals.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, vertex_positions.Length, vertex_normals.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_color.Length * sizeof(float));
            Marshal.Copy(vertex_color, 0, p, vertex_color.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, vertex_positions.Length + vertex_normals.Length, vertex_color.Length, p);
            Marshal.FreeHGlobal(p);
            p = Marshal.AllocHGlobal(vertex_objectid.Length * sizeof(float));
            Marshal.Copy(vertex_objectid, 0, p, vertex_objectid.Length);
            gl.BufferSubData(OpenGL.GL_ARRAY_BUFFER, vertex_positions.Length + vertex_normals.Length + vertex_color.Length, vertex_objectid.Length, p);
            Marshal.FreeHGlobal(p);
            gl.VertexAttribPointer(0, 4, OpenGL.GL_FLOAT, false, 0, (IntPtr)null);
            gl.VertexAttribPointer(1, 4, OpenGL.GL_FLOAT, false, 0, (IntPtr)(vertex_positions.Length));
            gl.VertexAttribPointer(2, 4, OpenGL.GL_FLOAT, false, 0, (IntPtr)(vertex_positions.Length + vertex_normals.Length));
            gl.VertexAttribPointer(3, 1, OpenGL.GL_FLOAT, false, 0, (IntPtr)(vertex_positions.Length + vertex_normals.Length + vertex_color.Length));
            gl.EnableVertexAttribArray(0);
            gl.EnableVertexAttribArray(1);
            gl.EnableVertexAttribArray(2);
            gl.EnableVertexAttribArray(3);
            gl.GenFramebuffersEXT(1, depth_fbo); 
            gl.Enable(OpenGL.GL_TEXTURE);
            depth_texture.Create(gl);
            depth_texture.Bind(gl);

            gl.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, OpenGL.GL_DEPTH_COMPONENT32, (int)(width*Settings.ssaa), (int)(height*Settings.ssaa), 0, OpenGL.GL_DEPTH_COMPONENT, OpenGL.GL_FLOAT, null);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MIN_FILTER, OpenGL.GL_LINEAR);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_MAG_FILTER, OpenGL.GL_LINEAR);
            //gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_COMPARE_MODE, OpenGL.GL_COMPARE_REF_TO_TEXTURE);
            //gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_COMPARE_FUNC, OpenGL.GL_LEQUAL);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_S, OpenGL.GL_CLAMP_TO_EDGE);
            gl.TexParameter(OpenGL.GL_TEXTURE_2D, OpenGL.GL_TEXTURE_WRAP_T, OpenGL.GL_CLAMP_TO_EDGE);

            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, depth_fbo[0]);
            gl.FramebufferTexture2DEXT(OpenGL.GL_FRAMEBUFFER_EXT, OpenGL.GL_DEPTH_ATTACHMENT_EXT, OpenGL.GL_TEXTURE_2D, depth_texture.TextureName, 0);
            gl.DrawBuffer(OpenGL.GL_NONE);
            gl.ReadBuffer(OpenGL.GL_NONE);
            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, 0);
            shaderProgram.Bind(gl);
        }
        public void DrawPrepare(OpenGL gl) {
            
            gl.Enable(OpenGL.GL_TEXTURE0);
            gl.ActiveTexture(OpenGL.GL_TEXTURE0);
            depth_texture.Bind(gl);
        }
        /// <summary>
        /// Draws the scene.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        private void SetUniform(OpenGL gl, ShaderProgram shader,float width,float height) {
            const float rads = (60 / 360.0f) * (float)Math.PI * 2.0f;
            vec4[] ortho = new vec4[4];
            ortho[0] = new vec4(1 / width * 10, 0, 0, 0);
            ortho[1] = new vec4(0, 1 / height * 10, 0, 0);
            ortho[2] = new vec4(0, 0, -0.00001f, 0);
            ortho[3] = new vec4(0, 0, 0, 1f);
            projectionMatrix = new mat4(ortho);
            projectionMatrix = glm.perspective(rads, width / height, 0.01f, 100);
            //  Create a view matrix to move us back a bit.
            viewMatrix = glm.rotate(new mat4(1.0f), (float)Camera.ya * Settings.reverse, new vec3(1, 0, 0));
            viewMatrix = glm.rotate(viewMatrix, (float)Camera.xa * Settings.reverse, new vec3(0, 1, 0));
            viewMatrix = glm.translate(viewMatrix, new vec3((float)Camera.x, (float)Camera.y, (float)Camera.z));
            vec3 cameraPosition = new vec3((float)Camera.x, (float)Camera.y, (float)Camera.z);
            //  Create a model matrix to make the model a little bigger.
            modelMatrix = glm.scale(new mat4(1.0f), new vec3(1f));
            mat4 lightView = glm.rotate(new mat4(1.0f), (float)Math.Atan(Camera.lighty / Camera.lightz), new vec3(1, 0, 0));
            lightView = glm.rotate(lightView, -(float)Math.Atan(Camera.lightx / Camera.lightz), new vec3(0, 1, 0));
            lightView = glm.translate(lightView, new vec3((float)Camera.lightx, (float)Camera.lighty, (float)Camera.lightz));
            gl.Uniform1(shader.GetUniformLocation(gl, "shadowMap"), 0);
            gl.UniformMatrix4(shader.GetUniformLocation(gl, "projection_matrix"), 1, false, projectionMatrix.to_array());
            gl.Uniform1(shader.GetUniformLocation(gl, "pcf"), Settings.PCF);
            gl.Uniform1(shader.GetUniformLocation(gl, "ortho"), 1);
            gl.Uniform1(shader.GetUniformLocation(gl, "selectId"), Util.selectId);
            gl.Uniform1(shader.GetUniformLocation(gl, "diff"), 100, diffuse);
            gl.Uniform1(shader.GetUniformLocation(gl, "ambient"), 100, ambient);
            gl.Uniform1(shader.GetUniformLocation(gl, "specular"), 100, specular);
            gl.Uniform1(shader.GetUniformLocation(gl, "visible"), 100, visible);
            shader.SetUniformMatrix4(gl, "view_matrix", viewMatrix.to_array());
            shader.SetUniformMatrix4(gl, "model_matrix", modelMatrix.to_array());
            shader.SetUniformMatrix4(gl, "lightView", lightView.to_array());
            shader.SetUniform3(gl, "camera_position", -cameraPosition[0], -cameraPosition[1], -cameraPosition[2]);
            shader.SetUniform3(gl, "light_color", 1.0f, 1.0f, 1.0f);
            shader.SetUniform3(gl, "light_position", -Camera.lightx, -Camera.lighty, -Camera.lightz);
        }
        public void Draw(OpenGL gl, float width, float height) {
            gl.TexImage2D(OpenGL.GL_TEXTURE_2D, 0, OpenGL.GL_DEPTH_COMPONENT32, (int)(width * Settings.ssaa), (int)(height * Settings.ssaa), 0, OpenGL.GL_DEPTH_COMPONENT, OpenGL.GL_FLOAT, null);

            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, 0);
            {
                SelectShaderProgram.Bind(gl);
                gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);
                gl.Viewport(0, 0, (int)width, (int)height);
                SetUniform(gl, SelectShaderProgram, width, height);
                SelectShaderProgram.AssertValid(gl);
                gl.Enable(OpenGL.GL_CULL_FACE);
                gl.Enable(OpenGL.GL_DEPTH_TEST);
                gl.DepthFunc(OpenGL.GL_LEQUAL);
                gl.DrawArrays(OpenGL.GL_TRIANGLES, 0, facesNum * 3);
                SelectShaderProgram.Unbind(gl);
                byte[] selectedPixel = new byte[4];
                gl.ReadPixels(Util.mouseX, Util.mouseY, 1, 1, OpenGL.GL_RGBA, OpenGL.GL_BYTE, selectedPixel);
                Util.selectId = selectedPixel[2] < objNum ? selectedPixel[2] : 0;
                if (Util.selectId != 0) {
                    Hide(Util.selectId);
                }
            }
            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, 0); 
            {
                SimpleShaderProgram.Bind(gl);
                gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);
                gl.Viewport(0, 0, (int)width, (int)height);
                SetUniform(gl, SimpleShaderProgram, width, height);
                SimpleShaderProgram.AssertValid(gl);
                gl.Enable(OpenGL.GL_CULL_FACE);
                gl.Enable(OpenGL.GL_DEPTH_TEST);
                gl.DepthFunc(OpenGL.GL_LEQUAL);
                gl.DrawArrays(OpenGL.GL_TRIANGLES, 0, facesNum * 3);
                SimpleShaderProgram.Unbind(gl);
            }
            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, depth_fbo[0]);
            {
                ShadowMapProgram.Bind(gl);
                var Status = gl.CheckFramebufferStatusEXT(OpenGL.GL_FRAMEBUFFER_EXT);
                if (Status != OpenGL.GL_FRAMEBUFFER_COMPLETE_EXT) {
                    MessageBox.Show("!");
                }
                gl.Viewport(0, 0, (int)(width * Settings.ssaa), (int)(height * Settings.ssaa));
                gl.Clear(OpenGL.GL_DEPTH_BUFFER_BIT);
                SetUniform(gl, ShadowMapProgram, width, height);
                ShadowMapProgram.AssertValid(gl);
                gl.Enable(OpenGL.GL_CULL_FACE);
                gl.CullFace(OpenGL.GL_FRONT);
                gl.Enable(OpenGL.GL_DEPTH_TEST);
                gl.DepthFunc(OpenGL.GL_LEQUAL);
                gl.DrawArrays(OpenGL.GL_TRIANGLES, 0, facesNum * 3);
                ShadowMapProgram.Unbind(gl);
                gl.CullFace(OpenGL.GL_BACK);
            }
            gl.BindFramebufferEXT(OpenGL.GL_FRAMEBUFFER_EXT, 0);
            {
                DrawPrepare(gl);
                shaderProgram.Bind(gl);
                gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT | OpenGL.GL_STENCIL_BUFFER_BIT);
                gl.Viewport(0, 0, (int)width, (int)height);
                SetUniform(gl, shaderProgram, width, height);
                shaderProgram.AssertValid(gl);
                gl.Enable(OpenGL.GL_CULL_FACE);
                gl.CullFace(OpenGL.GL_BACK);
                gl.Enable(OpenGL.GL_DEPTH_TEST);
                gl.DepthFunc(OpenGL.GL_LEQUAL);
                //  Draw the square.
                gl.DrawArrays(OpenGL.GL_TRIANGLES, 0, facesNum * 3);
                //  Unbind our vertex array and shader.
                shaderProgram.Unbind(gl);
            }
        }

        /// <summary>
        /// Creates the geometry for the square, also creating the vertex buffer array.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public static int time = 0;
        private ShaderProgram SimpleShaderProgram;

        private void CreateVerticesForSquare(OpenGL gl)
        {
            var vertices = new float[18];
            var colors = new float[18]; // Colors for our vertices  
            vertices[0] = -0.5f; vertices[1] = -0.5f; vertices[2] = 0.0f; // Bottom left corner  
            colors[0] = 1.0f; colors[1] = 1.0f; colors[2] = 1.0f; // Bottom left corner  
            vertices[3] = -0.5f; vertices[4] = 0.5f; vertices[5] = 0.0f; // Top left corner  
            colors[3] = 1.0f; colors[4] = 0.0f; colors[5] = 0.0f; // Top left corner  
            vertices[6] = 0.5f; vertices[7] = 0.5f; vertices[8] = 0.0f; // Top Right corner  
            colors[6] = 0.0f; colors[7] = 1.0f; colors[8] = 0.0f; // Top Right corner  
            vertices[9] = 0.5f; vertices[10] = -0.5f; vertices[11] = 0.0f; // Bottom right corner  
            colors[9] = 0.0f; colors[10] = 0.0f; colors[11] = 1.0f; // Bottom right corner  
            vertices[12] = -0.5f; vertices[13] = -0.5f; vertices[14] = 0.0f; // Bottom left corner  
            colors[12] = 1.0f; colors[13] = 1.0f; colors[14] = 1.0f; // Bottom left corner  
            vertices[15] = 0.5f; vertices[16] = 0.5f; vertices[17] = 0.0f; // Top Right corner  
            colors[15] = 0.0f; colors[16] = 1.0f; colors[17] = 0.0f; // Top Right corner  

            //  Create the vertex array object.
            vertexBufferArray = new VertexBufferArray();
            vertexBufferArray.Create(gl);
            vertexBufferArray.Bind(gl);

            //  Create a vertex buffer for the vertex data.
            var vertexDataBuffer = new VertexBuffer();
            vertexDataBuffer.Create(gl);
            vertexDataBuffer.Bind(gl);
            vertexDataBuffer.SetData(gl, 0, vertices, false, 3);

            //  Now do the same for the colour data.
            var colourDataBuffer = new VertexBuffer();
            colourDataBuffer.Create(gl);
            colourDataBuffer.Bind(gl);
            colourDataBuffer.SetData(gl, 1, colors, false, 3);

            //  Unbind the vertex array, we've finished specifying data for it.
            vertexBufferArray.Unbind(gl);
        }

    }
}
